﻿#region Libraries

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;

#endregion

namespace JBeamLibrary
{
    internal class JBeam
    {
        #region Fields

        #endregion

        #region Properties

        public string Filepath { get; set; }

        public JObject Root { get; private set; }

        #endregion

        #region CTORs

        public JBeam(string filename)
        {
            Root = new JObject();
            Filepath = filename;
        }

        public JBeam()
        {
            Root = new JObject();
        }

        #endregion

        #region Instance methods

        public void AddRootObject(JProperty jproperty)
        {
            Root.Add(jproperty);
        }

        public void ClearJBeam()
        {
            Root.RemoveAll();
        }

        public JObject LoadJbeam()
        {
            return Filepath == null ? Root : LoadJbeam(Filepath);
        }

        public JObject LoadJbeam(string filename)
        {
            var streamReader = new StreamReader(filename);
            var end = streamReader.ReadToEnd();
            streamReader.Close();
            Root = JObject.Parse(JbeamToJson(end));
            return Root;
        }

        public void SaveJbeam(bool compress = false, params string[] patterns)
        {
            if (Filepath != null)
            {
                SaveJbeam(Filepath, compress, patterns);
            }
        }

        public void SaveJbeam(string alternativeFilepath, bool compress = false, params string[] patterns)
        {
            /* Prepare contents */
            var contents = Root.ToString(Formatting.None);

            /* Use regex with the specified patterns */
            if (compress && patterns != null && patterns.Length > 0 && patterns.Length % 2 == 0)
            {
                for (var i = 0; i < patterns.Length; i += 2)
                    contents = ApplyPattern(contents, patterns[i], patterns[i + 1]);
            }

            /* Indent */
            if (!compress)
            {
                var builder = new StringBuilder();
                var lastChar = (char)0;
                var level = 0;
                var insideMain = false;
                var insidePart = false;
                var insideGroup = false;
                var insideObject = false;
                var insideMore = 0;
                var charCount = 0;
                var len = contents.Length;
                foreach (var c in contents)
                {
                    switch (c)
                    {
                        case '[':
                        case '{':
                            if (!insideObject && !insideGroup)
                                builder.AppendFormat("{2}{0}{1}", Environment.NewLine, new string('\t', ++level), c);
                            else builder.Append(c);

                            if (!insideMain) insideMain = true;
                            else if (!insidePart) insidePart = true;
                            else if (!insideGroup) insideGroup = true;
                            else if (!insideObject) insideObject = true;
                            else insideMore++;
                            break;

                        case ']':
                        case '}':
                            if (charCount == len - 1)
                                builder.AppendFormat("{0}{1}", Environment.NewLine, c);
                            else builder.Append(c);

                            if (insideMore > 0) insideMore--;
                            else if (insideObject) insideObject = false;
                            else if (insideGroup) insideGroup = false;
                            else if (insidePart) insidePart = false;
                            else insideMain = false;
                            break;

                        case ',':
                            if (!insideObject)
                            {
                                if ((lastChar == '}' || lastChar == ']') && !insideGroup)
                                {
                                    level--;
                                    if (!insidePart) level--;
                                    builder.AppendFormat("{2}{0}{0}{1}", Environment.NewLine, new string('\t', level), c);
                                }
                                else builder.AppendFormat("{2}{0}{1}", Environment.NewLine, new string('\t', level), c);
                            }
                            else builder.AppendFormat("{0} ", c);
                            break;

                        default:
                            builder.Append(c);
                            break;
                    }
                    lastChar = c;
                    charCount++;
                }
                contents = builder.ToString();
            }

            /* Write contents to file */
            using (var streamWriter = new StreamWriter(alternativeFilepath, false))
            {
                streamWriter.Write(contents);
                streamWriter.Close();
            }
        }

        #endregion

        #region Static methods

        private static string JbeamToJson(string jbeamtext)
        {
            jbeamtext = Regex.Replace(jbeamtext, "//.*\\r\\n", (Match match) => "");
            jbeamtext = Regex.Replace(jbeamtext, "/\\*[^\\*]*\\*/", "");
            jbeamtext = Regex.Replace(jbeamtext, "[a-zA-Z0-9\\]\\}]\\s*?\\r\\n",
                (Match match) => string.Concat(match.ToString()[0], ",\r\n"));
            jbeamtext = Regex.Replace(jbeamtext, "((?:[^\"]*?([0-9])[\\s]+([\\-0-9])[^\"]*?)*)(\"[^\"]*?\"|\\Z)",
                (Match match) =>
                {
                    var item = match.Groups[1];
                    var group = match.Groups[4];
                    return
                        string.Concat(
                            Regex.Replace(Regex.Replace(item.Value, "([0-9])[\\s]+([\\-0-9])", "$1,$2"),
                                "([0-9])[\\s]+([\\-0-9])", "$1,$2"), group.Value);
                });
            jbeamtext = Regex.Replace(jbeamtext, ",[\\s\\r\\n]*?\\]", "]");
            jbeamtext = Regex.Replace(jbeamtext, ",[\\s\\r\\n]*?\\}", "}");
            jbeamtext = Regex.Replace(jbeamtext, ",[\\r\\n\\s]*\\Z", "");
            jbeamtext = Regex.Replace(jbeamtext, "\\}[\\r\\n\\s]*?\"", "},\"");
            jbeamtext = Regex.Replace(jbeamtext, "\\][\\r\\n\\s]*?\"", "],\"");
            jbeamtext = Regex.Replace(jbeamtext, "((?<!\\s*:\\s*)\")[\\s]*\"", "$1,\"");
            return jbeamtext;
        }

        private static string ApplyPattern(string contents, string pattern, string replacement)
        {
            return Regex.Replace(contents, pattern, replacement, RegexOptions.Multiline);
        }

        #endregion
    }
}